#include "CVGlutViewer.h"
#include "GlutCallbacks.h"
#include <string>
static float scalefactor = 1;
void CCVGlutViewer::OnKey(uchar key, int x, int y)
{
	switch (key)
	{
	case 'm':
		ToggleCameraMove();
		break;
	case 'r':
		m_CVApp.Reset();
		break;
	case 'a':
	case 'w':
	case 's':
	case 'd':
	case 'z':
	case 'x':
		MoveViewCam(key);
		break;
	case 'q':
		CONSOLE("bye bye\n");
		exit(0);
		break;
	case '?':
		DisplayHelp();
		break;
	case'p':
		GetViewCamera().m_stop = !GetViewCamera().m_stop;
		break;
	case 'f':
		m_bDrawFocusPoint = !m_bDrawFocusPoint;
		break;
	case '\t':
		GetViewCamera().SetMoseMoveForward(true);
		break;
	case 't':
		ScaleCam(1, 0.1f);
		break;
	case 'y':
		ScaleCam(1, -0.1f);
		break;
	case 'g':
		ScaleCam(0, 0.1f);
		break;
	case 'h':
		ScaleCam(0, -0.1f);
		break;
	default:
		break;
	}
}

void CCVGlutViewer::ScaleCam( int i, float delta )
{
	m_PickInfo.scalefactor[i] += delta;
	UINT camIndex = m_PickInfo.cameraPicked[i];
	VISIO_ASSERT(camIndex < m_MeshInstances.size() - 1)
	CCameraMeshInstance* pInst = (CCameraMeshInstance*)m_MeshInstances[camIndex];

	pInst->SetScale(m_PickInfo.scalefactor[i]);

}

void CCVGlutViewer::OnUpKey(uchar key, int x, int y)
{
	switch (key)
	{
	case '\t':
		GetViewCamera().SetMoseMoveForward(false);
		break;
	}
}

void CCVGlutViewer::RunPhotoStich(char* SceneParams)
{
	m_CVApp.RunPhotoStich(SceneParams);

	m_CVApp.LoadMeshes(&m_MeshInstances);

	SetGeometricParameters();
}

void CCVGlutViewer::SetGeometricParameters()
{
	CPointCloudMeshInstance* pPCInst = (CPointCloudMeshInstance*)m_MeshInstances.back();
	SBoundingBox PCBBox = pPCInst->GetBoundingBox();

	m_MeshCenter = (PCBBox.Max + PCBBox.Min) * 0.5;
	float Radius = (PCBBox.Max - PCBBox.Min).Norm();
	Vector3f ViewPosition = Vector3f(Radius * sin(M_PI) + m_MeshCenter.x, m_MeshCenter.y, Radius*cos(M_PI) + m_MeshCenter.z);
	m_ViewerCamera.Init(30, 1024, 768, 1, 1000, m_MeshCenter, ViewPosition, Vector3f(0,1,0));
}

void CCVGlutViewer::DisplayHelp()
{
	CONSOLE("\nHelp\n");
	CONSOLE("---------------------------\n");
	CONSOLE("w, a, s, d, z, x, - Camera Movement\n");
	CONSOLE("m - enable mouse movement of camera\n");
	CONSOLE("q - Exit\n");
	CONSOLE("t - Enable drawing of focus point");
	CONSOLE("---------------------------\n");
}

void CCVGlutViewer::MoveViewCam(uchar key)
{
	m_ViewerCamera.Move(key);
}

void CCVGlutViewer::LoadImagesFromFile(FILE* pImgFile)
{
	UINT NumImages = 0;
	fscanf(pImgFile, "%d" , &NumImages);

	for (UINT i = 0; i < NumImages; i++)
	{
		char ImgName[200];
		fscanf(pImgFile, "%s", ImgName);

		cv::Mat Img = cv::imread(ImgName);

		VISIO_ASSERT(Img.data);
		m_Images.push_back(Img);
	}
}

void CCVGlutViewer::LoadSceneFromFile(char* SceneName)
{
	FILE* ImagesFile = CCVApp::GetImageFile(SceneName, "r");
	LoadImagesFromFile(ImagesFile);
	CBundlerParser Parser((CCameraMesh*)m_Meshes[0], &m_Images);
	Parser.ParseBundle(SceneName, &m_MeshInstances);

	SetGeometricParameters();
	
}

void CCVGlutViewer::Run()
{
	glutMainLoop();
}
void CCVGlutViewer::MouseWheel(int dir)
{
	m_ViewerCamera.Zoom(dir);
}

void CCVGlutViewer::RenderPicking()
{
	const int clIndex = 0x7fffffff;
	float clearcol[4];
	int clearIdx[] = {clIndex,clIndex,clIndex, clIndex};
	glGetFloatv(GL_COLOR_CLEAR_VALUE, clearcol);
	

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
// 	glClearColor(0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF);
	glClear(GL_DEPTH_BUFFER_BIT);
	glClearBufferiv(GL_COLOR, 0, clearIdx);
	for (UINT i = 0; i < m_MeshInstances.size(); i++)
	{
		CMeshInstance* pInst = m_MeshInstances[i];
		pInst->DrawPicking();
	}

	glClearColor(clearcol[0], clearcol[0], clearcol[2], clearcol[3]);

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

void CCVGlutViewer::Render()
{
	static float time = 0;
	time += 0.1f;
	m_ViewerCamera.OnRender(m_bDrawFocusPoint);
	for (UINT i = 0; i < m_MeshInstances.size(); i++)
	{
		CMeshInstance* pInst = m_MeshInstances[i];
		pInst->Update(time, m_ViewerCamera);
		pInst->Draw();
	}

	RenderPicking();
}

void CCVGlutViewer::Test()
{
	double points[3*3] = {0.0 ,0.0, 0.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.5};
	/*CPointCloudMesh* pMesh = new CPointCloudMesh(points, 3);
	m_MeshInstances.push_back(pMesh->CreateInstance());*/
}

void CCVGlutViewer::CreatePopMenu()
{
	//Create the file menu
	m_MenuMap["File"] = glutCreateMenu(FileMenuHandlerWrapper);
	glutAddMenuEntry("Open Scene", FILEMENU_OPEN_SCENE);
	glutAddMenuEntry("Create Scene", FILEMENU_CREATE_SCENE);
	m_MenuMap["Viewer"] = glutCreateMenu(ViewerMenuHandlerWrapper);
	glutAddMenuEntry("Background Color", VIEWERMENU_BG_COLOR);

	m_MenuMap["Main"] = glutCreateMenu(NULL);
	glutAddSubMenu("File", m_MenuMap["File"]);
	glutAddSubMenu("Viewer", m_MenuMap["Viewer"]);
	glutAttachMenu(GLUT_MIDDLE_BUTTON);
}

void CCVGlutViewer::CVGlInitPicking(UINT width, UINT height)
{
	// Create the FBO
	glGenFramebuffers(1, &m_fbo);    
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);

	// Create the texture object for the primitive information buffer
	glGenTextures(1, &m_pickingTexture);
	glBindTexture(GL_TEXTURE_2D, m_pickingTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32UI, width, height, 0, GL_RGB_INTEGER, GL_INT, NULL);
	glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pickingTexture, 0);    

	// Create the texture object for the depth buffer
	glGenTextures(1, &m_depthTexture);
	glBindTexture(GL_TEXTURE_2D, m_depthTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
	glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthTexture, 0);    

	// Verify that the FBO is correct
	GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

	if (Status != GL_FRAMEBUFFER_COMPLETE) {
		CONSOLE("FB error, status: 0x%x\n", Status);
	}

	// Restore the default framebuffer
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);


	ZeroMemory(&m_PickInfo, sizeof(PickingAndScaleInfo));
	for (int i = 0; i < 2; i++)
	{
		m_PickInfo.cameraPicked[i] = 0xffffffff;
		m_PickInfo.scalefactor[i] = 1.0f;
	}
}

void CCVGlutViewer::Init(int* argc, char** argv)
{
	const UINT width = 1024;
	const UINT height = 768;
	//init GLut
	glutInit(argc, argv);
	glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA);
	glutInitWindowSize(width, height);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("CV");
	
	InitializeGlutCallbacks(this);

	CreatePopMenu();

	GLenum res = glewInit();
	if (res != GLEW_OK)
	{
		CONSOLE("Error: '%s'\n", glewGetErrorString(res));
		exit(1);
	}

	CVGlInitPicking(width, height);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

	//init some other stuff
	CCameraMesh* pCam = new CCameraMesh;
	m_Meshes.push_back(pCam);

	m_ViewerCamera.Init(30, width, height, 1, 1000, Vector3f(0,0,1), Vector3f(0,0,-1), Vector3f(0,1,0));
	m_CVApp.Init(pCam);
	m_bMoveCam = false;
	m_MouseMode = STAIONARY;
	m_rgbCurrent = RGB(0, 0, 0);
	m_bMenuUsed = false;
	m_bDrawFocusPoint = true;
}

void CCVGlutViewer::OpenSceneFile()
{
	//TODO: use the common dialog class instead of this old api same for color
	OPENFILENAME ofn;
	char szFileName[MAX_PATH] = "";

	ZeroMemory(&ofn, sizeof(ofn));

	ofn.lStructSize = sizeof(ofn); // SEE NOTE BELOW
	ofn.hwndOwner = 0;
	ofn.lpstrFilter = "Scene Files (*.scene)\0*.scene\0All Files (*.*)\0*.*\0";
	ofn.lpstrFile = szFileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	ofn.lpstrDefExt = "scene";

	if (GetOpenFileName(&ofn))
	{
		LoadSceneFromFile(ofn.lpstrFile);
	}
}

void CCVGlutViewer::FileMenuHandler( int value )
{
	if (!m_bMenuUsed)
	{
		m_bMenuUsed = true;
		switch (value)
		{
		case FILEMENU_OPEN_SCENE:
			OpenSceneFile();
			break;
		case FILEMENU_CREATE_SCENE:
			CreateScene();
			break;
		default:
			break;
		}
		m_bMenuUsed = false;
	}
	
}

bool CCVGlutViewer::GetSceneFileName(char* FileName)
{
	OPENFILENAME ofn;

	ZeroMemory(&ofn, sizeof(ofn));

	ofn.lStructSize = sizeof(ofn); // SEE NOTE BELOW
	ofn.hwndOwner = 0;
	ofn.lpstrFilter = "CV In Files (*.cin)\0*.cin\0All Files (*.*)\0*.*\0";
	ofn.lpstrFile = FileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	ofn.lpstrDefExt = "cin";

	return GetOpenFileName(&ofn);
}

void CCVGlutViewer::CreateScene()
{
	char szFileName[MAX_PATH] = "";
	if (GetSceneFileName(szFileName))
	{

		CONSOLE("Running Photo stich\n");
		RunPhotoStich(szFileName);
		CONSOLE("Finished Photo stich\n");
	}
}

void CCVGlutViewer::ViewerMenuHandler( int value )
{
	if (!m_bMenuUsed)
	{
		m_bMenuUsed = true;
		switch (value)
		{
		case VIEWERMENU_BG_COLOR:
			ChooseBGColor();
			break;
		default:
			break;
		}
		m_bMenuUsed = false;
	}
}

void CCVGlutViewer::ChooseBGColor()
{
	CHOOSECOLOR cc;                 // common dialog box structure 

	// Initialize CHOOSECOLOR 
	ZeroMemory(&cc, sizeof(cc));
	cc.lStructSize = sizeof(cc);
	cc.hwndOwner = NULL;
	cc.lpCustColors = (LPDWORD) m_acrCustClr;
	cc.rgbResult = m_rgbCurrent;
	cc.Flags = CC_FULLOPEN | CC_RGBINIT;

	if (ChooseColor(&cc)==TRUE) 
	{
		m_rgbCurrent = cc.rgbResult;
		float r = (float)GetRValue(m_rgbCurrent) / 255.0f;
		float g = (float)GetGValue(m_rgbCurrent) / 255.0f;
		float b = (float)GetBValue(m_rgbCurrent) / 255.0f;
		glClearColor(r, g, b, 1.0);
	}
}

void CCVGlutViewer::MouseMotion(int x, int y)
{
	switch (m_MouseMode)
	{
	case ORBIT:
		GetViewCamera().Orbit(x, y, GetViewCamera().GetFocusPoint());
		break;
	case TRANSLATE:
		GetViewCamera().Move(x, y);
		break;
	}
}

void CCVGlutViewer::StartPicking(int x, int y)
{
	static const Vector3f pickingColor[3] = {Vector3f(0,1,0), Vector3f(0,0,1), Vector3f(1,0,0)};

	glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
	glReadBuffer(GL_COLOR_ATTACHMENT0);

	int Pixel[3];
	glReadPixels(x, y, 1, 1, GL_RGB_INTEGER, GL_INT, Pixel);

	if (Pixel[0] != 0x7fffffff)
	{
		UINT& CamIndex = m_PickInfo.cameraPicked[m_PickInfo.currSlot];
		if (m_PickInfo.cameraPicked[m_PickInfo.currSlot] < m_MeshInstances.size() - 1)
		{
			CCameraMeshInstance* pInst = (CCameraMeshInstance*)m_MeshInstances[CamIndex];
			m_PickInfo.scalefactor[m_PickInfo.currSlot] = 1.0;
			pInst->SetScale(m_PickInfo.scalefactor[m_PickInfo.currSlot]);
			pInst->SetColor(pickingColor[2]);
		}

		CamIndex = UINT(Pixel[0]);
		CCameraMeshInstance* pInst = (CCameraMeshInstance*)m_MeshInstances[CamIndex];
		pInst->SetColor(pickingColor[m_PickInfo.currSlot]);
		m_PickInfo.currSlot = m_PickInfo.currSlot ^ 0x1;
	}

	glReadBuffer(GL_NONE);
	glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);

}

void CCVGlutViewer::MouseClick(int button, int state, int x, int y)
{
	// only start motion if the left button is pressed
	switch (button) 
	{
	case GLUT_RIGHT_BUTTON:
		// when the button is released
		if (state == GLUT_UP) {
			m_MouseMode = STAIONARY;
		}
		else  {// state = GLUT_DOWN
			m_ViewerCamera.UpdateMousePos(x, y);
			m_MouseMode = ORBIT;
		}
		break;
	case GLUT_LEFT_BUTTON:
		// when the button is released
		if (state == GLUT_UP) {
			m_MouseMode = STAIONARY;
			StartPicking(x, 768 - y - 1);
		}
		else  {// state = GLUT_DOWN
			m_ViewerCamera.UpdateMousePos(x, y);
			m_MouseMode = TRANSLATE;
		}
		break;
	}
	
}